# AUTHORIZE.NET CIM STORAGE FOR CREDIT CARD
# we can implement only six transaction types acording Customer Information Manager (CIM) 
#    XML Guide: Authorization Only, Authorization and Capture, Capture Only, Prior Authorization and Capture, Refund and Void.
# With ActiveMerchant for AUTHORIZE.NET CIM STORAGE we can implement only Authorization Only, Authorization and Capture, Capture Only.

class BillingAmCreditCard < ActiveMerchant::Billing::CreditCard
  # include ZepFrog::Utils

  CC_OUTSIDE = true # ZepFrog::Config[:credit_card_outside]  # use it not in this classs but in configuration for determine that all credit cards stores in outside storage = true or in our database = false
  CC_BILLING_VALIDATE = true # (ZepFrog::Config[:credit_card_validate_transaction] || false)   # real billing validation (real transaction in auth_only mode for amount 0.01) !!! finish and set true
  
  attr_reader :out_options
  
  
   # requires!(options, :interval, :duration, :billing_address)
   #    requires!(options[:interval], :length, [:unit, :days, :months])
   #    requires!(options[:duration], :start_date, :occurrences)
   #    requires!(options[:billing_address], :first_name, :last_name)
   #  
  
  def initialize(options ={})
    requires!(options, :main)
    
    super options[:main]
    # put some validations from CreditCard
    
    @options ||= options[:main].merge(options[:add_options] || {:empty_add =>""})
    @out_options = {} # Hash of results
    
    @options[:user_profile] = user_profile    
  end

  # create customer profile
  def customer_profile
    @customer_profile =  get_customer_profile || 
                        ACCOUNT_GATEWAY.create_customer_profile({:profile => {:email => @options[:user_profile]}})

    unless @customer_profile.success?
      # self.errors.add "customer_profile", @customer_profile.message
      out_params({:variable_name => "customer_profile", :success => false, :msg => @customer_profile.message})
      raise ZepFrog::PaymentError, @customer_profile.message
    end
    out_params({:customer_profile_id => @customer_profile.authorization.to_s, :variable_name => "customer_profile",
                :success => true, :msg => @customer_profile.message})
  end


  # create customer payment profile
  def customer_payment_profile
    # assert_equal @customer_payment_profile_id, response.params['customer_payment_profile_id']
    if @customer_profile || (@options[:customer_profile_id] || @out_options[:customer_profile_id])
      @customer_payment_profile =   ACCOUNT_GATEWAY.create_customer_payment_profile(
                                      :customer_profile_id => (@options[:customer_profile_id] || @out_options[:customer_profile_id]),
                                      :payment_profile => {
                                        :payment => {:credit_card => self},
                                        # add bill_to and ship_to later
                                        :bill_to => {:zip => @options[:zipcode]}
                                 })

      unless @customer_payment_profile.params['customer_payment_profile_id']
        # self.errors.add "customer_payment_profile", @customer_payment_profile.message
        out_params({:variable_name => "customer_payment_profile",  
                    :success => false, :msg => @customer_payment_profile.message})
        raise ZepFrog::PaymentError, @customer_payment_profile.message
      else
        out_params({:customer_payment_profile_id => @customer_payment_profile.params['customer_payment_profile_id'],
                    :variable_name => "customer_payment_profile",
                    :success => true, :msg => @customer_payment_profile.message})
      end
      
      # real validation using transaction 0.01
      begin
        validate_customer_payment_profile if CC_BILLING_VALIDATE
      rescue ZepFrog::PaymentError => e
        self.delete_customer_payment_profile({:no_msg => "yes"})
        out_params({:variable_name => "customer_payment_profile::validate_customer_payment_profile",  
                    :success => false})
      
        raise ZepFrog::PaymentError, @out_options[:msg]
      end
      
    end
  end
  
  # real validation using transaction 0.01 (06/03/2009 The ActiveMerchant Error - instead 0.01 it equals 1$)
  def validate_customer_payment_profile
    response = ACCOUNT_GATEWAY.validate_customer_payment_profile(
                            :customer_profile_id => (@options[:customer_profile_id] || @out_options[:customer_profile_id]),
                            :customer_payment_profile_id => (@options[:customer_payment_profile_id] || @out_options[:customer_payment_profile_id]),
                            :customer_address_id => nil,  # do later validation for address,
                            :validation_mode => :live     # for test mode -  :test :  :validation_mode — :live or :test In Test Mode, only field validation is performed. In Live Mode, a transaction is generated and submitted to the processor with the amount of $0.01. If successful, the transaction is immediately voided. (REQUIRED)
                            )
                            
    if response.success?
      out_params({:variable_name => "validate_customer_payment_profile",
                  :success => true, :msg => (response.message)}) #  response.params['direct_response']['raw'] || 
    else
      # 06082009 Olga - ActiveMerchant Authorize.net CIm bug
      # gives wrong error msg too long
      # err_msg = response.params['direct_response']['message'].length > 50 ? "Please, check account information carefully." : response.params['direct_response']['message']
      # # err_msg = "Check account information carefully."
      # puts response.message
      out_params({:variable_name => "validate_customer_payment_profile",
                  :success => false, :msg => response.message, :log_msg => response.params['direct_response']['raw'], :full_response => response.to_s})
      raise ZepFrog::PaymentError, response.message
    end
  end

  # delete credit card
  def delete_customer_payment_profile(options={})
    response = ACCOUNT_GATEWAY.delete_customer_payment_profile(
                            :customer_profile_id => (@options[:customer_profile_id] || @out_options[:customer_profile_id]),
                            :customer_payment_profile_id => (@options[:customer_payment_profile_id] || @out_options[:customer_payment_profile_id])
                          )
    if response.success?
      out_params({:variable_name => "delete_customer_payment_profile",
                  :success => true, :msg => response.message}) if options[:no_msg].nil?
    else
      out_params({:variable_name => "delete_customer_payment_profile",
                  :success => false, :msg => response.message})
      raise ZepFrog::PaymentError, response.message
    end
  end
  
  # delete credit card
  def self.delete_customer_payment_profile(options={})
    requires!(options, :customer_profile_id, :customer_payment_profile_id)
    response = ACCOUNT_GATEWAY.delete_customer_payment_profile(
                            :customer_profile_id => (options[:customer_profile_id]),
                            :customer_payment_profile_id => (options[:customer_payment_profile_id])
                          )
    if response.success?
      out_params({:variable_name => "delete_customer_payment_profile",
                  :success => true, :msg => response.message})
    else
      out_params({:variable_name => "delete_customer_payment_profile",
                  :success => false, :msg => response.message}) #,  :log_msg => response.params['direct_response']['raw']})
      raise ZepFrog::PaymentError, response.message
    end
  end

  # delete customer profile
  def delete_customer_profile(options={})
    if @customer_profile || @out_options[:customer_profile_id] || @options[:customer_profile_id]
      response = ACCOUNT_GATEWAY.delete_customer_profile({
                        :customer_profile_id => (@options[:customer_profile_id] || @out_options[:customer_profile_id])})
      if response.success?
        out_params({:variable_name => "delete_customer_profile",
                    :success => true, :msg => response.message}) if options[:no_msg].nil?
      else
        out_params({:variable_name => "delete_customer_profile",
                    :success => false, :msg => response.message})
        raise ZepFrog::PaymentError, response.message
      end
    end
  end
  
  # check will it work as class method not instance method
  def self.delete_customer_profile(options={})
    requires!(options, :customer_profile_id)
    response = ACCOUNT_GATEWAY.delete_customer_profile({
                        :customer_profile_id => (options[:customer_profile_id])})
    unless response.success?
      out_params({:variable_name => "delete_customer_profile",
                  :success => false, :msg => response.message})
      self.errors.add "customer_profile", response.message
      raise ZepFrog::PaymentError, response.message
    else
      out_params({:variable_name => "delete_customer_profile",
                  :success => true, :msg => response.message})  if options[:no_msg].nil?
    end
  end
  
  # create profile/get existing and create(always new) credit _account
  def store_profile_credit_card
    begin
      self.customer_profile    
      self.customer_payment_profile
    rescue ZepFrog::PaymentError => e
      self.delete_customer_profile({:no_msg => "no"}) #if @options[:customer_profile_id].nil?
      raise ZepFrog::PaymentError, e.inspect
    end
    @out_options
  end
  
  def update_customer_profile    
    requires!(@options, :customer_profile_id)
    
    @customer_profile = get_customer_profile
    
    if @customer_profile
      response = ACCOUNT_GATEWAY.update_customer_profile(
                                      {:profile => {:customer_profile_id => @options[:customer_profile_id],
                                                    :email => @options[:user_profile]}})
                               
      unless response.success?
        out_params({:variable_name => "update_customer_profile",
                    :success => false, :msg => response.message})
        self.errors.add "update_customer_profile", response.message
        raise ZepFrog::PaymentError, response.message
      else
        out_params({:variable_name => "update_customer_profile",
                    :success => true, :msg => response.message})
      end
    else
      self.errors.add "update_customer_profile", @customer_profile.message
      raise ZepFrog::PaymentError, @customer_profile.message
    end
  end
  
  def update_customer_payment_profile
    requires!(@options, :customer_profile_id, :customer_payment_profile_id)
 
    @customer_profile = get_customer_profile
    @customer_payment_profile = get_customer_payment_profile
    
    if @customer_profile && @customer_payment_profile
      response = ACCOUNT_GATEWAY.update_customer_payment_profile(
                                      :customer_profile_id => @options[:customer_profile_id],
                                      :payment_profile => {
                                        :customer_payment_profile_id => @options[:customer_payment_profile_id],
                                        :payment => {:credit_card => self},
                                        # add bill_to and ship_to later
                                        :bill_to => {:zip => @options[:zipcode]}
                                 })
                               
      unless response.success? # || response.params['customer_payment_profile_id'])
        out_params({:customer_profile_id =>  @options[:customer_profile_id],
                    :customer_payment_profile_id => @options[:customer_payment_profile_id],
                    :variable_name => "update_customer_payment_profile",
                    :success => false, :msg => response.message})
        self.errors.add "update_customer_payment_profile", response.message
        raise ZepFrog::PaymentError, response.message
      else
        out_params({:customer_profile_id => (@customer_profile.authorization || @options[:customer_profile_id]),
                    :customer_payment_profile_id => (@customer_payment_profile.params['customer_payment_profile_id'] || @options[:customer_payment_profile_id]),
                    :variable_name => "update_customer_payment_profile",
                    :success => true, :msg => response.message})
      end
      
      # real validation using transaction 0.01
      begin
        validate_customer_payment_profile if CC_BILLING_VALIDATE
      rescue ZepFrog::PaymentError => e
        self.delete_customer_payment_profile({:no_msg => "yes"})
        out_params({:variable_name => "update_customer_payment_profile::validate_customer_payment_profile",  
                    :success => false})
      
        raise ZepFrog::PaymentError, @out_options[:msg]
      end
      
    else
      unless @customer_profile.success?
        self.errors.add "update_customer_payment_profile", @customer_profile.message
        raise ZepFrog::PaymentError, @customer_profile.message
      end
      unless @customer_payment_profile.success?
        self.errors.add "update_customer_payment_profile", @customer_payment_profile.message
        raise ZepFrog::PaymentError, @customer_payment_profile.message
      end
    end
  end
  

  private
  
  # generating random user profile : must be unique for Authorize.net CIM
  def user_profile
    today = Date.today
    (@options[:user_id] || random_id).to_s + "__" + (@options[:last_name] || "") + "__" + today.to_s
  end
  
  # generating random id
  def random_id
    "R#{Array.new(9){rand(9)}.join}"
  end
  
  # options [:variable_name, :success, :msg, :log_msg]
  def out_params(options)
    unless options.nil? && options.is_a(Hash)
      if @out_options.nil?
        @out_options = options
      else
        @out_options.merge!(options)
      end
      @out_options
    end
  end
  
  def self.out_params(options)
    unless options.nil? && options.is_a(Hash)
      @out_options
    end
  end
  
  # check with update and delete
  def get_customer_profile
    if @options[:customer_profile_id]
      return ACCOUNT_GATEWAY.get_customer_profile({:customer_profile_id => @options[:customer_profile_id]})
      # !!!!!! check record cant be found error
    else
      @customer_profile
    end
  end
  
  def get_customer_payment_profile
    if @options[:customer_profile_id] && @options[:customer_payment_profile_id]
      return ACCOUNT_GATEWAY.get_customer_payment_profile({
                                            :customer_profile_id => @options[:customer_profile_id],
                                            :customer_payment_profile_id => (@options[:customer_payment_profile_id])
                                            })
      # !!!!!! check record cant be found error
    else
      @customer_payment_profile
    end
  end
  
  def self.requires!(hash, *params)
    params.each do |param| 
      if param.is_a?(Array)
        raise ArgumentError.new("Missing required parameter: #{param.first}") unless hash.has_key?(param.first) 
  
        valid_options = param[1..-1]
        raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(:connector => 'or')}") unless valid_options.include?(hash[param.first])
      else
        raise ArgumentError.new("Missing required parameter: #{param}") unless hash.has_key?(param) 
      end
    end
  end
  
  def requires!(hash, *params)
    params.each do |param| 
      if param.is_a?(Array)
        raise ArgumentError.new("Missing required parameter: #{param.first}") unless hash.has_key?(param.first) 
  
        valid_options = param[1..-1]
        raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(:connector => 'or')}") unless valid_options.include?(hash[param.first])
      else
        raise ArgumentError.new("Missing required parameter: #{param}") unless hash.has_key?(param) 
      end
    end
  end
  
end
